﻿using ChineseAreaService;
using Microsoft.AspNet.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.OptionsModel;
using Sino.CapacityCloud.Services.Models;
using Sino.Tms.WebApi.Framework.Client;
using Sino.WebApi.Framework.Client;
using Sino.WebApi.Framework.Primitives;
using Sino.WebApi.Framework.Protocol;
using System;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Sino.CapacityCloud.Repositories.Models;
using Sino.Tms.Primitives;
using System.Collections.Generic;
using System.Reflection;
using AutoMapper;
using Sino.CapacityCloud.WebApi;

using Order = Sino.CapacityCloud.Repositories.Models.Order;

namespace Sino.CapacityCloud.Services
{

    public class TmsServiceBus
        : TmsRestfulApiClient, ITmsServiceBus
    {

        #region Constants
        #endregion

        #region Fields
        protected readonly ILogger _logger;

        protected readonly IChinaAreaService _areaService;
        #endregion

        #region Properties
        public override TimeSpan DefaultTimeout
        {
            get
            {
                return TimeSpan.FromMilliseconds(-1);
            }
        }

        public override bool IsNetworkAvailable
        {
            get
            {
                try
                {
                    return Plugin.Connectivity.CrossConnectivity.Current.IsConnected;
                }
                catch (Exception ex)
                {
                    this._logger.LogError(ex.Message + Environment.NewLine + ex.StackTrace);
                    return true;
                }
            }
        }
        #endregion

        #region Constructors
        public TmsServiceBus(IHostingEnvironment env, ILoggerFactory loggerFactory, IChinaAreaService areaService)
            : this(LoadSettings(env, "Data:DefaultConnection:TmsServiceBus"))
        {
            this._areaService = areaService;
            this._logger = loggerFactory.CreateLogger<TmsServiceBus>();
            this.Initialize();
        }

        protected TmsServiceBus(IOptions<TmsServiceBusOptions> options)
           : base(options.Value.BaseUrl, options.Value.AppId, options.Value.AppSecrect, options.Value.AccessTimeoutRetries)
        {
        }

        protected TmsServiceBus(TmsServiceBusOptions options)
            : base(options.BaseUrl, options.AppId, options.AppSecrect, options.AccessTimeoutRetries)
        {
        }

        protected TmsServiceBus(string baseUrl, string appId, string appSecrect, uint accessTimeoutRetries = DefaultAccessTimeoutRetries)
            : base(baseUrl, appId, appSecrect, accessTimeoutRetries)
        {
        }

        /// <summary>
        /// Finalizer for the TmsServiceBus.
        /// </summary>
        ~TmsServiceBus()
        {
            Dispose(false);
        }

        public override bool Initialize()
        {
            var r = base.Initialize();
            this.AuthorizeAsync();
            return r;
        }
        #endregion

        #region Web API
        #endregion

        #region Wrappers
        public async Task<PageVM<Order>> GetOrdersAsync(string shipaddress = null, string deliveraddress = null, DateTime? shipstarttime = default(DateTime?), DateTime? shipendtime = default(DateTime?), int skip = 0, int count = -1)
        {
            var list = new List<Order>();
            var total = 0;
            try
            {
                var result = await this.GetOrdersAsync(shipaddress, deliveraddress, null, null, shipstarttime, shipendtime, skip, count);
                if (result?.Output?.List?.Count() > 0)
                {
                    total = result.Output.Total;
                    list = result.Output.List.Select(x =>
                        Mapper.Map(x, new Order())).ToList();
                }
            }
            catch (Exception ex)
            {
                this._logger.LogError(ex.Message + Environment.NewLine + ex.StackTrace);
                list = new List<Order>();
                total = 0;
            }
            return await Task.FromResult(new PageVM<Order>()
            {
                List = list,
                Total = total
            });
        }

        public async Task<PageVM<ConfirmedOrder>> GetMyOrdersAsync(string userId, string carCode, int skip = 0, int count = -1)
        {
            var list = new List<ConfirmedOrder>();
            var total = 0;
            try
            {
                var result = await this.GetCarrierOrdersAsync(userId, skip, count);
                if (result?.Output?.List?.Count() > 0)
                {
                    total = result.Output.Total;
                    list = result.Output.List.Select(z =>
                    {
                        if (z?.List == null || z.List.Count() == 0)
                        {
                            return null;
                        }
                        if (z.List.Count() > 1)
                        {
                            var parent = new ConfirmedOrder();
                            var subOrders = z.List.Select(x =>
                                Mapper.Map(x, new ConfirmedOrder())).ToList();
                            var firstSubOrder = subOrders.FirstOrDefault();
                            firstSubOrder.CopyPropertiesTo(
                                parent, nameof(parent.ConfirmedOrderId),
                                nameof(parent.SubOrders), nameof(parent.Parent));
                            parent.ConfirmedOrderId = firstSubOrder.CarrierOrderId;
                            parent.SubOrders = subOrders;
                            parent.CarCode = carCode;
                            parent.Type = OrderType.Breakbulk;
                            return parent;
                        }
                        else
                        {
                            var order = z.List.FirstOrDefault();
                            var r = Mapper.Map(order, new ConfirmedOrder());
                            r.CarCode = carCode;
                            r.Type = OrderType.Bulk;
                            return r;
                        }
                    }).Where(x => x != null).ToList();
                }
            }
            catch (Exception ex)
            {
                this._logger.LogError(ex.Message + Environment.NewLine + ex.StackTrace);
                list = new List<ConfirmedOrder>();
                total = 0;
            }
            return await Task.FromResult(new PageVM<ConfirmedOrder>()
            {
                List = list,
                Total = total
            });
        }

        public async Task<ConfirmedOrder> GetMyOrderDetailsAsync(string carrierOrderId)
        {
            try
            {
                var result = await this.GetCarrierOrderDetailsAsync(carrierOrderId);
                if (result?.Output?.List?.Count() > 0)
                {
                    var z = result.Output;
                    if (z?.List == null || z.List.Count() == 0)
                    {
                        return null;
                    }
                    if (z.List.Count() > 1)
                    {
                        var parent = new ConfirmedOrder();
                        var subOrders = z.List.Select(x =>
                            Mapper.Map(x, new ConfirmedOrder())).ToList();
                        var firstSubOrder = subOrders.FirstOrDefault();
                        firstSubOrder.CopyPropertiesTo(
                            parent, nameof(parent.ConfirmedOrderId),
                            nameof(parent.SubOrders), nameof(parent.Parent));
                        parent.ConfirmedOrderId = firstSubOrder.CarrierOrderId;
                        parent.SubOrders = subOrders;
                        parent.Type = OrderType.Breakbulk;
                        return parent;
                    }
                    else
                    {
                        var order = z.List.FirstOrDefault();
                        var r = Mapper.Map(order, new ConfirmedOrder());
                        r.Type = OrderType.Bulk;
                        return r;
                    }
                }
                return null;
            }
            catch (Exception ex)
            {
                this._logger.LogError(ex.Message + Environment.NewLine + ex.StackTrace);
                return null;
            }
        }

        public async Task<PageVM<Order>> GetMyInvolvedOrdersAsync(IEnumerable<string> orderIds, string shipaddress = null, string deliveraddress = null, DateTime? shipstarttime = default(DateTime?), DateTime? shipendtime = default(DateTime?), int skip = 0, int count = -1)
        {
            var list = new List<Order>();
            var total = 0;
            try
            {
                var result = await this.GetGrabbedOrdersAsync(orderIds, shipaddress, deliveraddress, shipstarttime, shipendtime, skip, count);
                if (result?.Output?.List?.Count() > 0)
                {
                    total = result.Output.Total;
                    list = result.Output.List.Select(x =>
                        Mapper.Map(x, new Order())).ToList();
                }
            }
            catch (Exception ex)
            {
                this._logger.LogError(ex.Message + Environment.NewLine + ex.StackTrace);
                list = new List<Order>();
                total = 0;
            }
            return await Task.FromResult(new PageVM<Order>()
            {
                List = list,
                Total = total
            });
        }

        public async Task<IEnumerable<ConfirmedOrder>> GetCarrierSubOrdersAsync(string carrierorderid)
        {
            var list = new List<ConfirmedOrder>();
            var total = 0;
            try
            {
                var result = await this.GetCarrierOrderDetailsAsync(carrierorderid);
                if (result?.Output?.List?.Count() > 0)
                {
                    total = result.Output.Total;
                    list = result.Output.List.Select(x =>
                    {
                        var order = Mapper.Map(x, new ConfirmedOrder());
                        order.Type = OrderType.Suborder;
                        return order;
                    }).ToList();
                }
            }
            catch (Exception ex)
            {
                this._logger.LogError(ex.Message + Environment.NewLine + ex.StackTrace);
                list = new List<ConfirmedOrder>();
                total = 0;
            }
            return await Task.FromResult(list);
        }

        public async Task<IEnumerable<RatingRecord>> QueryRatingRecordsAsync(string carriedOrderId, string orderId, string carId, string userId)
        {
            var list = new List<RatingRecord>();
            var total = 0;
            try
            {
                var result = await this.QueryEvaluationsAsync(carriedOrderId, userId);
                if (result?.Output?.List?.Count() > 0)
                {
                    total = result.Output.Total;
                    list = result.Output.List.Select(x =>
                    {
                        var cost = Mapper.Map(x, new RatingRecord());
                        cost.CarrierOrderId = carriedOrderId;
                        cost.OrderId = orderId;
                        cost.CarId = carId;
                        return cost;
                    }).ToList();
                }
            }
            catch (Exception ex)
            {
                this._logger.LogError(ex.Message + Environment.NewLine + ex.StackTrace);
                list = new List<RatingRecord>();
                total = 0;
            }
            return await Task.FromResult(list);
        }

        public async Task<IEnumerable<PayableCost>> QueryPayableCostsAsync(string carriedOrderId, string carId, string userId)
        {
            var list = new List<PayableCost>();
            var total = 0;
            try
            {
                var result = await this.QueryFeesAsync(carriedOrderId, userId);
                if (result?.Output?.List?.Count() > 0)
                {
                    total = result.Output.Total;
                    list = result.Output.List.Select(x =>
                    {
                        var cost = Mapper.Map(x, new PayableCost());
                        cost.CarrierOrderId = carriedOrderId;
                        return cost;
                    }).ToList();
                }
            }
            catch (Exception ex)
            {
                this._logger.LogError(ex.Message + Environment.NewLine + ex.StackTrace);
                list = new List<PayableCost>();
                total = 0;
            }
            return await Task.FromResult(list);
        }
        #endregion

        #region Utilities
        protected override MessageHandlePolicy DefaultExceptionHandler<TReturnValue>(Exception exception, out TReturnValue returnValue)
        {
            return base.DefaultExceptionHandler<TReturnValue>(exception, out returnValue);
        }

        protected async override Task<TReturnValue> DefaultResponseProcessor<TResponse, TReturnValue>(ResponsePack<TResponse> obj, Func<ResponsePack<TResponse>, CancellationToken, Task<TReturnValue>> converter, CancellationToken token)
        {
            if (obj.Error != null)
            {
                throw new ProtocolViolationException($"Error {obj.Error.Code}: {obj.Error.Message}");
            }
            return await base.DefaultResponseProcessor<TResponse, TReturnValue>(obj, converter, token);
        }

        protected override void OnTrace(object sender, string message, object tag = null)
        {
            this._logger.LogInformation($"{sender.GetType().FullName}: {message}\r\n{tag?.ToJson()}");
        }

        protected override void OnError(object sender, string message, Exception ex, object tag = null)
        {
            if (ex != null && (ex is AccessTokenTimeoutException || ex.InnerException is AccessTokenTimeoutException))
            {
                var token = this.AuthorizeAsync().GetAwaiter().GetResult();
                this._logger.LogError($"{sender.GetType().FullName}: {message}\r\n{token}", ex);
            }
            else
            {
                this._logger.LogError($"{sender.GetType().FullName}: {message}\r\n{tag?.ToJson()}", ex);
            }
        }

        protected static TmsServiceBusOptions LoadSettings(IHostingEnvironment env, string sectionName, string configurationFileName = "appsettings")
        {
            var configurationBuilder = new ConfigurationBuilder()
             .AddJsonFile($"{configurationFileName}.json")
             .AddJsonFile($"{configurationFileName}.{env.EnvironmentName}.json", optional: true)
             .AddEnvironmentVariables();

            var configuration = configurationBuilder.Build().GetSection(sectionName);

            return new TmsServiceBusOptions(configuration["BaseUrl"], configuration["AppKey"], configuration["AppSecret"], uint.Parse(configuration["AccessTimeoutRetries"]));
        }
        #endregion

        #region IDisposable
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
        }
        #endregion

    }

}